﻿using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Abp.Web;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Linq;
using System.Transactions;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
using System.Reflection;

namespace Abp.NHibernate
{
    public class NhRepository<TEntity> : IRepository<TEntity> where TEntity : class,IEntity
    {

        protected static readonly ConcurrentDictionary<Type, List<PropertyInfo>> _paramCache = new ConcurrentDictionary<Type, List<PropertyInfo>>();
        protected ISession Session { get; private set; }

        public NhRepository()
        {
            Session = NhibernateHelper.GetCurrentSession();
        }

        public IQueryable<TEntity> Tables
        {
            get
            {
                return Session.Query<TEntity>().Cacheable();
            }
        }

        public int Delete(TEntity entity)
        {
           this.Session.Delete(entity);
           this.Session.Flush();
           return 1;
        }

        public int Delete(object id)
        {
            return Delete(this.Session.Load<TEntity>(id));
        }

        public int Delete(IEnumerable<object> ids)
        {
            TransactionScope(()=>{
                foreach (var id in ids)
                {
                    this.Session.Delete(this.Session.Load<TEntity>(id));
                }
                this.Session.Flush();
            });
            return ids.Count();
        }


       

        public int Delete(Expression<Func<TEntity, bool>> where)
        {
            var query = Where(where);

            TransactionScope(() => {

                foreach (var entity in query)
                {
                    this.Session.Delete(entity);
                }
                this.Session.Flush();

            });
         
            return query.Count();
        }

        public TEntity Get(object id)
        {
            return this.Session.Get<TEntity>(id);
                
        }

        public List<TEntity> GetByPage(Pagination pagnation, Action<Orderable<TEntity>> orderby)
        {
            IQueryable<TEntity> query = this.Tables;
            if (!string.IsNullOrWhiteSpace(pagnation.Order) && !string.IsNullOrWhiteSpace(pagnation.Sort))
            {
                MethodCallExpression resultExp = null;
                var t =typeof(TEntity);
               if (!_paramCache.ContainsKey(t))
                {
                    _paramCache[t] = t.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public).ToList();
                }
                if (!_paramCache[t].Any(info => string.Compare(info.Name, pagnation.Sort,true)==0))
                {
                    throw new Exception("排序字段不存在！");
                }
                bool isAsc = pagnation.Order.ToLower() == "asc" ? true : false;
                var parameter = Expression.Parameter(typeof(TEntity), "t");
                var property = typeof(TEntity).GetProperty(pagnation.Sort);
                var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                var orderByExp = Expression.Lambda(propertyAccess, parameter);
                resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(TEntity), property.PropertyType }, query.Expression, Expression.Quote(orderByExp));
                query=query.Provider.CreateQuery<TEntity>(resultExp);
                pagnation.Total = query.Count();
                return query.Skip(pagnation.GetStartRecordIndex())
                    .Take(pagnation.GetPageSize())
                    .ToList();
            }


            var orderable = new Orderable<TEntity>(query);
            orderby(orderable);
            query = orderable.Queryable;
            pagnation.Total = query.Count();
            return query.Skip(pagnation.GetStartRecordIndex())
                .Take(pagnation.GetPageSize())
                .ToList();
        }

        public List<TEntity> GetByPage(Pagination pagnation, Expression<Func<TEntity, bool>> where, Action<Orderable<TEntity>> orderby)
        {
            IQueryable<TEntity> query = null;
            if (!string.IsNullOrWhiteSpace(pagnation.Order) && !string.IsNullOrWhiteSpace(pagnation.Sort))
            {
                MethodCallExpression resultExp = null;
                var t = typeof(TEntity);
                if (!_paramCache.ContainsKey(t))
                {
                    _paramCache[t] = t.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public).ToList();
                }
                if (!_paramCache[t].Any(info => string.Compare(info.Name, pagnation.Sort, true) == 0))
                {
                    throw new Exception("排序字段不存在！");
                }
                query = Where(where);
                bool isAsc = pagnation.Order.ToLower() == "asc" ? true : false;
                var parameter = Expression.Parameter(typeof(TEntity), "t");
                var property = typeof(TEntity).GetProperty(pagnation.Sort);
                var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                var orderByExp = Expression.Lambda(propertyAccess, parameter);
                resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(TEntity), property.PropertyType }, query.Expression, Expression.Quote(orderByExp));
                query=query.Provider.CreateQuery<TEntity>(resultExp);
                pagnation.Total = query.Count();
                return query.Skip(pagnation.GetStartRecordIndex())
                    .Take(pagnation.GetPageSize())
                    .ToList();
            }


            query = this.Where(where, orderby);
            pagnation.Total = query.Count();
            return query.Skip(pagnation.GetStartRecordIndex())
                .Take(pagnation.GetPageSize())
                .ToList();
        }

        public TEntity Insert(TEntity entity)
        {
            this.Session.Save(entity);
            this.Session.Flush();
            return entity;
        }

        public int Insert(IEnumerable<TEntity> entities)
        {
            TransactionScope(() => {

                foreach (var entity in entities)
                {
                    this.Session.Save(entity);
                }
                this.Session.Flush();

            });
         
            return entities.Count();
        }

        public TEntity Update(TEntity entity)
        {
            this.Session.Evict(entity);
            this.Session.Merge(entity);
            this.Session.Flush();
            return entity;
        }

        public int Update(Expression<Func<TEntity, bool>> where, Action<TEntity> action)
        {
            var query = Where(where);
            TransactionScope(()=> {

                foreach (var entity in query)
                {
                    action(entity);
                    this.Session.Evict(entity);
                    this.Session.Merge(entity);
                }
                this.Session.Flush();

            });
            
            return query.Count();
        }

        public IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> where)
        {
            return this.Tables.Where(where) ;
        }

        public IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> where, Action<Orderable<TEntity>> orderby)
        {
            var orderable = new Orderable<TEntity>(this.Tables.Where(where));
            orderby(orderable);
            return orderable.Queryable;
        }

        protected void TransactionScope(Action action)
        {
            TransactionScope scope = new TransactionScope(TransactionScopeOption.Required,TimeSpan.FromMinutes(10));
            try
            {
                action();
                scope.Complete();

            }
            catch
            {
                throw;
            }
            finally
            {
                scope.Dispose();
            }
        }
    }
}
